package com.onlyxiahui.framework.net.handler.connect;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.onlyxiahui.framework.net.handler.connect.action.ConnectBackAction;
import com.onlyxiahui.framework.net.handler.connect.action.ConnectStatusAction;
import com.onlyxiahui.framework.net.handler.connect.data.ConnectData;
import com.onlyxiahui.framework.net.handler.connect.type.IdleStatus;
import com.onlyxiahui.framework.net.handler.thread.HandlerThreadPoolBuilder;

/**
 * Description <br>
 * Date 2020-05-11 11:11:02<br>
 * 
 * @author XiaHui [onlovexiahui@qq.com]<br>
 * @since 1.0.0
 */

public class ConnectHandler {

	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	private final long maxCount = Long.MAX_VALUE - 10;

	private ExecutorService pool = HandlerThreadPoolBuilder.build();
	/**
	 * 返回信息队列
	 */
	protected final ConcurrentLinkedQueue<ConnectBackAction> backQueue = new ConcurrentLinkedQueue<ConnectBackAction>();
	protected final Set<ConnectStatusAction> connectStatusActionSet = new CopyOnWriteArraySet<ConnectStatusAction>();

	/******************************************************/
	/**
	 * 连接中
	 */
	protected boolean connecting = false;
	/**
	 * 已连接
	 */
	protected boolean connected = false;
	/**
	 * 连接临时状态，用于状态变化
	 */
	protected boolean connectedTemp = false;
	/**
	 * 成功连接的时间戳(毫秒)
	 */
	protected long connectedTimestamp = 0;

	/******************************************************/
	/**
	 * 自动连接执行中
	 */
	protected boolean autoConnecting = false;
	/**
	 * 已重试次数
	 */
	protected long autoConnectTryCount = 0;
	/**
	 * 重试时间
	 */
	protected long autoConnectTimestamp = 0;

	/**
	 * 自动连接进入等待
	 */
	protected boolean autoConnectWait = false;

	/**
	 * 总重试次数
	 */
	protected long connectTryTotalCount = 0;
	/**
	 * 总连接次数记录
	 */
	protected long connectedTotalCount = 0;

	/******************************************************/

	private long idleHandledTimestamp = 0;

	/******************************************************/

	/************* -------------- ****************/

	/**
	 * 是否自动连接
	 */
	private boolean autoConnect = false;

	/**
	 * 当连接断开，系统自动连接尝试重试次数
	 */
	private long autoConnectLimitCount = 30;
	/**
	 * 重试间隔时间（毫秒）
	 */
	private long autoConnectIntervalTimestamp = 1000 * 60 * 2;
	/**
	 * 空闲间隔（毫秒）
	 */
	private long idleIntervalTimestamp = 1000 * 10;

	/**
	 * 自动断开连接时间，比如客服端进行登录失败后，一直没有关闭程序，就会设置自动断开时间（大于0便执行）
	 */
	private long autoCloseConnectMillisecond = -1;
	/**
	 * 记录开始设置自动断开时间的时间，用来计算时间是否达到。
	 */
	private long autoCloseConnectTimestamp = 0;
	/**
	 * 连接服务器信息
	 */
	private ConnectData connectData;
	/**
	 * 连接对象
	 */
	private Connector connector;

	/************* -------------- ****************/
	public ConnectHandler() {
	}

	/************* base getter setter ****************/

	public boolean isConnecting() {
		return connecting;
	}

	public long getConnectedTimestamp() {
		return connectedTimestamp;
	}

	public boolean isAutoConnect() {
		return autoConnect;
	}

	public void setAutoConnect(boolean autoConnect) {
		this.autoConnect = autoConnect;
	}

	public boolean isAutoConnecting() {
		return autoConnecting;
	}

	public long getAutoConnectLimitCount() {
		return autoConnectLimitCount;
	}

	public void setAutoConnectLimitCount(long autoConnectLimitCount) {
		this.autoConnectLimitCount = autoConnectLimitCount;
	}

	public long getAutoConnectIntervalTimestamp() {
		return autoConnectIntervalTimestamp;
	}

	public void setAutoConnectIntervalTimestamp(long autoConnectIntervalTimestamp) {
		this.autoConnectIntervalTimestamp = autoConnectIntervalTimestamp;
	}

	public long getAutoConnectTryCount() {
		return autoConnectTryCount;
	}

	public void setAutoConnectTryCount(long autoConnectTryCount) {
		this.autoConnectTryCount = autoConnectTryCount;
	}

	public long getAutoConnectTimestamp() {
		return autoConnectTimestamp;
	}

	public long getConnectTryTotalCount() {
		return connectTryTotalCount;
	}

	public long getConnectedTotalCount() {
		return connectedTotalCount;
	}

	public long getAutoCloseConnectMillisecond() {
		return autoCloseConnectMillisecond;
	}

	public void setAutoCloseConnectMillisecond(long autoCloseConnectMillisecond) {
		this.autoCloseConnectMillisecond = autoCloseConnectMillisecond;
		this.autoCloseConnectTimestamp = System.currentTimeMillis() + autoCloseConnectMillisecond;
	}

	public long getAutoCloseConnectTimestamp() {
		return autoCloseConnectTimestamp;
	}

	public ConnectData getConnectData() {
		return connectData;
	}

	public void setConnectData(ConnectData connectData) {
		this.connectData = connectData;
	}

	public Connector getConnector() {
		return connector;
	}

	public void setConnector(Connector connector) {
		this.connector = connector;
	}

	/************* base getter setter ****************/

	public boolean addConnectStatusAction(ConnectStatusAction action) {
		return connectStatusActionSet.add(action);
	}

	public boolean removeConnectStatusAction(ConnectStatusAction action) {
		return connectStatusActionSet.remove(action);
	}

	public Set<ConnectStatusAction> getConnectStatusActionSet() {
		return connectStatusActionSet;
	}

	public boolean isConnected() {
		return null != connector && connector.isConnected();
	}

	public IdleStatus getIdleStatus() {
		return (null != connector) ? connector.getIdleStatus() : null;
	}

	public void closeConnect() {
		autoConnect = false;
		if (null != connector) {
			connector.close();
		}
	}

	public boolean hasConnector() {
		return connector != null;
	}

	public boolean hasConnectData() {
		return connectData != null;
	}

	private void connectedTotalCountPlus() {
		connectedTotalCount = connectedTotalCount >= maxCount ? maxCount : connectedTotalCount + 1;
	}

	private void connectTryTotalCountPlus() {
		connectTryTotalCount = connectTryTotalCount >= maxCount ? maxCount : connectTryTotalCount + 1;
	}

	private boolean canConnect() {
		boolean can = (hasConnector() && hasConnectData());
		return can && !this.isConnecting();
	}

	private boolean canAutoConnect() {
		boolean can = (this.isAutoConnect() && hasConnector() && hasConnectData() && !this.isConnected());
		return can && !this.isConnecting();
	}

	public void handle() {
		try {
			handleConnectStatusChange();
			handleAutoConnect();
			handleIdle();
			handleAutoClose();
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	public void connect(ConnectData connectData) {
		this.connect(connectData, new ArrayList<>());
	}

	public void connect(ConnectData connectData, ConnectBackAction action) {
		List<ConnectBackAction> list = new ArrayList<>();
		if (null != action) {
			list.add(action);
		}
		this.connect(connectData, list);
	}

	public void connect(ConnectData connectData, List<ConnectBackAction> actions) {
		this.setConnectData(connectData);
		this.backQueue.addAll(actions);
		this.setAutoConnect(true);
		this.autoConnectTryCount = 0;
	}

	private boolean connect() {
		boolean can = canConnect();
		if (can) {
			connecting = true;
			try {
				// 获取连接会话
				connector.connect(connectData);
			} catch (Exception e) {
				logger.error("连接失败！总重试" + connectTryTotalCount + "次；当前连接重试" + autoConnectTryCount + "");
			}
			connecting = false;
		}
		return isConnected();
	}

	private void handleAutoConnect() {
		boolean autoConnect = this.canAutoConnect();
		if (autoConnect) {
			if (this.autoConnectTryCount < this.autoConnectLimitCount) {
				this.autoConnecting = true;
				this.autoConnectTryCount++;
				this.autoConnectTimestamp = System.currentTimeMillis();
				this.connectTryTotalCountPlus();
				this.connect();
				boolean connected = isConnected();
				if (connected) {
					handleAutoConnect(connected);
				}
				handleConnectStatusChange();
				handleConnectBack(isConnected());
			} else {
				this.autoConnecting = false;
				long intervalTimestamp = System.currentTimeMillis() - this.autoConnectTimestamp;
				// 默认2分钟尝试一次
				if (intervalTimestamp >= this.autoConnectIntervalTimestamp) {
					this.autoConnectWait = false;
					this.autoConnectTryCount = 0;
				} else {
					if (!this.autoConnectWait) {
						this.autoConnectWait = true;
						handleConnectStatusChange();
						handleAutoConnect(isConnected());
					}
				}
			}
		}
	}

	private void handleConnectStatusChange() {
		boolean isConnected = isConnected();
		if (isConnected != connectedTemp) {
			connectedTemp = isConnected;
			handleConnectStatusChange(isConnected);
		}
	}

	private void handleIdle() {
		boolean isConnected = this.isConnected();
		if (isConnected) {
			// 网络处于连接状态，并且一段时间没发信息了
			long timestamp = System.currentTimeMillis() - this.idleHandledTimestamp;
			if (timestamp > (this.idleIntervalTimestamp)) {
				IdleStatus idleStatus = this.getIdleStatus();
				if (null != idleStatus && IdleStatus.no != idleStatus) {
					handleIdle(idleStatus);
				}
				this.idleHandledTimestamp = System.currentTimeMillis();
			}
		}
	}

	protected void handleAutoClose() {
		if ((autoCloseConnectMillisecond > 0) && System.currentTimeMillis() > autoCloseConnectTimestamp) {
			autoCloseConnectMillisecond = -1;
			closeConnect();
		}
	}

	private void handleConnectBack(boolean success) {
		if (success) {
			connectedTotalCountPlus();
			connectedTimestamp = System.currentTimeMillis();
		}
		while (null != backQueue && backQueue.peek() != null) {
			ConnectBackAction connectAction = backQueue.poll();
			pool.execute(new Runnable() {

				@Override
				public void run() {
					try {
						connectAction.connectBack(success);
					} catch (Exception e) {
						logger.error("", e);
					}
				}
			});
		}
	}

	private void handleConnectStatusChange(boolean connected) {
		if (!connectStatusActionSet.isEmpty()) {
			for (ConnectStatusAction connectStatusAction : connectStatusActionSet) {
				pool.execute(new Runnable() {

					@Override
					public void run() {
						try {
							connectStatusAction.onStatusChange(connected);
						} catch (Exception e) {
							logger.error("", e);
						}
					}
				});
			}
		}
	}

	private void handleIdle(IdleStatus idleStatus) {
		if (!connectStatusActionSet.isEmpty()) {
			for (ConnectStatusAction connectStatusAction : connectStatusActionSet) {
				pool.execute(new Runnable() {

					@Override
					public void run() {
						try {
							connectStatusAction.onIdle(idleStatus);
						} catch (Exception e) {
							logger.error("", e);
						}
					}
				});
			}
		}
	}

	private void handleAutoConnect(boolean connected) {
		if (!connectStatusActionSet.isEmpty()) {
			for (ConnectStatusAction connectStatusAction : connectStatusActionSet) {
				pool.execute(new Runnable() {

					@Override
					public void run() {
						try {
							connectStatusAction.onAfterAutoConnect(connected);
						} catch (Exception e) {
							logger.error("", e);
						}
					}
				});
			}
		}
	}
}
